package com.thaiopensource.validate.auto;

import java.io.IOException;
import java.io.InputStream;

import javax.xml.transform.sax.SAXSource;

import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.thaiopensource.resolver.xml.sax.SAXResolver;
import com.thaiopensource.util.PropertyMap;
import com.thaiopensource.util.PropertyMapBuilder;
import com.thaiopensource.validate.AbstractSchemaReader;
import com.thaiopensource.validate.IncorrectSchemaException;
import com.thaiopensource.validate.Option;
import com.thaiopensource.validate.ResolverFactory;
import com.thaiopensource.validate.Schema;
import com.thaiopensource.validate.ValidateProperty;

public class AutoSchemaReader extends AbstractSchemaReader
{
  private final SchemaReceiverFactory srf;

  public AutoSchemaReader ()
  {
    this (new SchemaReceiverLoader ());
  }

  public AutoSchemaReader (final SchemaReceiverFactory srf)
  {
    this.srf = srf == null ? new SchemaReceiverLoader () : srf;
  }

  public Schema createSchema (final SAXSource source, PropertyMap properties) throws IOException,
                                                                             SAXException,
                                                                             IncorrectSchemaException
  {
    if (properties.get (SchemaReceiverFactory.PROPERTY) != srf)
    {
      final PropertyMapBuilder builder = new PropertyMapBuilder (properties);
      builder.put (SchemaReceiverFactory.PROPERTY, srf);
      properties = builder.toPropertyMap ();
    }
    final SAXResolver resolver = ResolverFactory.createResolver (properties);
    final InputSource in = resolver.open (source.getInputSource ());
    final InputSource in2 = new InputSource ();
    in2.setSystemId (in.getSystemId ());
    in2.setPublicId (in.getPublicId ());
    in2.setEncoding (in.getEncoding ());
    Rewindable rewindable;
    if (in.getCharacterStream () != null)
    {
      final RewindableReader rewindableReader = new RewindableReader (in.getCharacterStream ());
      in.setCharacterStream (rewindableReader);
      in2.setCharacterStream (rewindableReader);
      rewindable = rewindableReader;
    }
    else
    {
      final InputStream byteStream = in.getByteStream ();
      final RewindableInputStream rewindableByteStream = new RewindableInputStream (byteStream);
      in.setByteStream (rewindableByteStream);
      in2.setByteStream (rewindableByteStream);
      rewindable = rewindableByteStream;
    }
    final SchemaReceiver sr = new AutoSchemaReceiver (properties, rewindable);
    XMLReader xr = source.getXMLReader ();
    if (xr == null)
      xr = resolver.createXMLReader ();
    final ErrorHandler eh = properties.get (ValidateProperty.ERROR_HANDLER);
    if (eh != null)
      xr.setErrorHandler (eh);
    final SchemaFuture sf = sr.installHandlers (xr);
    try
    {
      try
      {
        xr.parse (in);
        return sf.getSchema ();
      }
      catch (final ReparseException e)
      {
        rewindable.rewind ();
        rewindable.willNotRewind ();
        return e.reparse (new SAXSource (xr, in2));
      }
      finally
      {
        rewindable.willNotRewind ();
      }
    }
    catch (final SAXException e)
    {
      // Work around broken SAX parsers that catch and wrap runtime exceptions
      // thrown by handlers
      final Exception nested = e.getException ();
      if (nested instanceof RuntimeException)
        sf.unwrapException ((RuntimeException) nested);
      throw e;
    }
    catch (final RuntimeException e)
    {
      throw sf.unwrapException (e);
    }
  }

  public Option getOption (final String uri)
  {
    return srf.getOption (uri);
  }
}
